package suncertify.db;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import suncertify.network.ServerInterface;
import suncertify.ui.Subscriber;

/**
 * The data object that handles all the I/O for the data file. 
 * @author Paul Wilhelm
 *
 */
public class Data implements DBMain, ServerInterface{
	
	private RandomAccessFile raf;			//Declare the RandomAccessFile
	private static Data instance;			//Singleton reference of a Data object
	private List<Subscriber> subscriptions = new ArrayList<Subscriber>();		//A list to hold subscribers to data changes
	private List<Integer> lockedRecords = new ArrayList<Integer>();				//A list to hold all the record numbers that are locked
	private String requestingName;					//The name of the latest room to initiate a data change
	private String requestingLocation;				//The location of the latest room to initiate a data change
	
	//Private, not a requirement to document
	private Data(){
		//For access control only
	}
	/**
	 * Singleton to make sure to pass the same <code>Data</code> object to each requesting class.
	 * @return the Data object if one exists.  Otherwise creates and returns a new Data object
	 */
	public static Data getInstance(){
		if (instance == null){			//If no Data object has been created
			instance = new Data();
		}
		return instance;
	}
	
	/**
	 * Reads in the new database file into a <code>RandomAccessFile</code>.
	 * @param dbLocation - The location of the database file
	 * @throws FileNotFoundException
	 */
	public void loadDbFile(String dbLocation) throws FileNotFoundException{
		File file = new File(dbLocation);				
		raf = new RandomAccessFile(file, "rw");			
	}
	
	/**
	 * {@inheritDoc}
	 */
	public int create(String[] data) throws DuplicateKeyException {
		// Not part of original requirements.  This functionality will not be added at this time
		return 0;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public void delete(int recNo) throws RecordNotFoundException {
		// Not part of original requirements.  This functionality will not be added at this time
	}
	
	/**
	 * {@inheritDoc}
	 */
	public synchronized int[] find(String[] criteria) throws RecordNotFoundException {
		String critName = criteria[0];
		String critLocation = criteria[1];
		List<Integer> matches = null;			
		try{
			List<Room> allRooms = getAllRooms();
			matches = new ArrayList<Integer>();  	
			for (Room r: allRooms){	
				if (r.getName().equals(critName) && r.getLocation().equals(critLocation)){		//If both values match, add the room to the list
					matches.add(allRooms.indexOf(r));
					continue;
				}
				if (critName.equals("") || critLocation.equals("")){			//If only one value is filled in
					if (r.getName().equals(critName)){			//If the name matches, add the room to the list
						matches.add(allRooms.indexOf(r));
						continue;
					}
					if (r.getLocation().equals(critLocation)){	//If the location matches, add the name to the list
						matches.add(allRooms.indexOf(r));
						continue;
					}
				}
			}
		} catch (IOException e){
			e.printStackTrace();
		}
		int[] results = new int[matches.size()];	//Cannot cast from Object to int[] so I need to iterate and add all the elements instead of using toArray
		for (int i = 0; i < matches.size(); i++){
			results[i] = matches.get(i);
		}
		return results;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public boolean isLocked(int recNo) throws RecordNotFoundException {
		return lockedRecords.contains(recNo);
	}
	
	/**
	 * {@inheritDoc}
	 */
	public synchronized void lock(int recNo) throws RecordNotFoundException {
		while (isLocked(recNo)){			//Constantly checks to see if the record is locked.  If so, it waits
			try{
				wait();
			} catch (InterruptedException ie){
				ie.printStackTrace();
			}
		}
		lockedRecords.add(recNo);			//When not locked, it adds it to the list of locked records
	}
	
	/**
	 * {@inheritDoc}
	 */
	public synchronized String[] read(int recNo) throws RecordNotFoundException{
		lock(recNo);
		String[] roomInfo = null;		
		try{						//Tries to read the particular record and store it as roomInfo 
			roomInfo = new String[getFieldCount()];
			Room room = getAllRooms().get(recNo);
			roomInfo = room.getAllInfo();
		} catch (IOException e){
			e.printStackTrace();
		}
		finally{
			unlock(recNo);
		}
		return roomInfo;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public synchronized void unlock(int recNo) throws RecordNotFoundException {
		lockedRecords.remove(new Integer(recNo));	
		notifyAll();				//Notifies all waiting records that they can now make changes
	}
	
	/**
	 * {@inheritDoc}
	 */
	public synchronized void update(int recNo, String[] data) throws RecordNotFoundException {
		lock(recNo);				
		String newField = data[0];		//Saves the information to be written as a String. Only updates the 
										//Customer ID as the requirements didn't specify differently
		try{
			int offset = getOffset();
			raf.seek(offset);
			int counter = 0;		
			while (raf.getFilePointer() < raf.length()){			//Iterates through the entire document
				raf.readShort();
				if (counter == recNo){		
					raf.skipBytes(151);		//Jumps to the correct position of the record
					raf.writeDouble(Double.parseDouble(newField));		
					break;			//No need to search the rest of the document
				}
				counter++;
				raf.skipBytes(159);		//Skips bytes to jump to the next record and check it
			}
		} catch (IOException e){
			e.printStackTrace();
		}
		finally{
			unlock(recNo);	
		}
	}
	
	/**
	 * Searches through an URLyBird <code>RandomAccessFile</code> and grabs the Magic Cookie value from the file.
	 * @return Magic cookie value as an int
	 * @throws IOException
	 */
	public int getMagicCookie() throws IOException{
		raf.seek(0);
		int cookie = raf.readInt();
		return cookie;
	}
	
	/**
	 * Searches through an URLyBird database <code>RandomAccessFile</code> and grabs the offSet value.
	 * @return The int value of the offSet
	 * @throws IOException
	 */
	public int getOffset() throws IOException{
		raf.seek(4);
		int offset = raf.readInt();
		return offset;
	}
	
	/**
	 * Searches through an URLyBird database <code>RandomAccessFile</code> and grabs the number of fields.
	 * @return The short value of the number of fields
	 * @throws IOException
	 */
	public short getFieldCount() throws IOException{
		raf.seek(8);
		short fieldCount = raf.readShort();
		return fieldCount;
	}
	
	/**
	 * Searches through an URLyBird database <code>RandomAccessFile</code> and grabs the field values for each field.
	 * @return A String[] of all the field names
	 * @throws IOException
	 */
	public String[] getFieldNames() throws IOException{
		int fieldCount = getFieldCount();
		String[] names = new String[fieldCount];
		raf.seek(10);
		for (int i = 0; i < fieldCount; i++){
			short fieldNameLength = raf.readShort();
			byte[] buffer = new byte[fieldNameLength];			
			raf.read(buffer);									
			names[i] = new String(buffer);
			raf.readShort();									
		}
		return names;
	}
	
	/**
	 * Searches through an URLyBird database <code>RandomAccessFile</code> and grabs the field lengths.
	 * @return an int[] of all the field lengths
	 * @throws IOException
	 */
	public int[] getFieldLengths() throws IOException{
		int fieldCount = getFieldCount();
		int[] fieldLengths = new int[fieldCount];
		raf.seek(10);
		for (int i = 0; i < fieldCount; i++){
			short fieldNameLength = raf.readShort();
			byte[] buffer = new byte[fieldNameLength];
			raf.read(buffer);							
			fieldLengths[i] = raf.readShort();			
		}
		return fieldLengths;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public Vector<Room> getAllRooms() throws IOException{
		Vector<Room> allRooms = new Vector<Room>();		
		int offset = getOffset();
		int[] fieldLengths = getFieldLengths();
		raf.seek(offset);
		while (raf.getFilePointer() < raf.length()){				//Iterates through the entire document
			raf.readShort();								
			String[] data = new String[fieldLengths.length];
			for (int i = 0; i < fieldLengths.length; i++){			//Iterates through the fields
				byte[] buffer = new byte[fieldLengths[i]];			
				raf.read(buffer);
				String s = new String(buffer).trim();
				data[i] = s;
			}
			Room room = new Room(data[0], data[1], data[2], data[3], data[4], data[5], data[6]);	
			allRooms.add(room);		
		}
		return allRooms;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public void notifySubscriptions() throws RemoteException {
		for (Subscriber s: subscriptions){
				s.update(requestingName, requestingLocation);			//Updates each view, passing it the name and location of the item that caused the data change
																		// This helps with the Observer pattern
		}
	}
	
	/**
	 * {@inheritDoc}
	 */
	public void subscribe(Subscriber s) throws RemoteException {
		subscriptions.add(s);		
	}
	
	/**
	 * {@inheritDoc}
	 */
	public void unSubscribe(Subscriber s) throws RemoteException {
		for (Iterator<Subscriber> it = subscriptions.iterator(); it.hasNext();){
			Subscriber subscriber = it.next();
			if (subscriber.equals(s)){
				it.remove();
			}
		}
	}
	
	/**
	 * {@inheritDoc}
	 */
	public void setRequestingName(String name){
		requestingName = name;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public void setRequestingLocation(String location){
		requestingLocation = location;
	}
	


}
